package org.teamswift.crow.rbac.service;

import org.apache.shiro.authz.Permission;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.teamswift.crow.rbac.common.ICrowUser;
import org.teamswift.crow.rbac.entity.*;
import org.teamswift.crow.rbac.repository.*;
import org.teamswift.crow.rbac.security.permission.CrowPermission;
import org.teamswift.crow.rest.common.ICrowService;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Lazy
@Transactional(rollbackFor = Exception.class)
public class CrowUserService implements ICrowService<Integer, CrowUser, CrowUserRepository> {

    private final CrowUserRepository userRepository;

    private final CrowUserRoleRepository crowUserRoleRepository;

    private final CrowRoleRepository crowRoleRepository;

    private final CrowAuthorityRepository crowAuthorityRepository;

    private final CrowPermissionResourceService crowPermissionResourceService;

    @Override
    public CrowUserRepository getRepository() {
        return userRepository;
    }

    public CrowUserService(CrowUserRepository userRepository, CrowUserRoleRepository crowUserRoleRepository, CrowRoleRepository crowRoleRepository, CrowAuthorityRepository crowAuthorityRepository, CrowPermissionResourceService crowPermissionResourceService) {
        this.userRepository = userRepository;
        this.crowUserRoleRepository = crowUserRoleRepository;
        this.crowRoleRepository = crowRoleRepository;
        this.crowAuthorityRepository = crowAuthorityRepository;
        this.crowPermissionResourceService = crowPermissionResourceService;
    }

    @Override
    public CrowUser findOneById(Integer integer) {
        CrowUser user = ICrowService.super.findOneById(integer);
        user.setRoles(getRoles(user));

        return user;
    }

    public Optional<CrowUser> findOneByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    public Optional<CrowUser> findOneByUsernameOrMobile(String username) {
        return userRepository.findByMobileOrUsername(username, username);
    }

    public Optional<CrowUser> findOneByMobile(String mobile) {
        return userRepository.findByMobile(mobile);
    }

    public Set<CrowRole> getRoles(ICrowUser<?> user) {
        List<CrowUserRole> userRoles = crowUserRoleRepository.findAllByUserIdAndOrgId(user.getId(), user.getOrganizationId());
        if(userRoles.size() == 0) {
            return new HashSet<>();
        }

        return new HashSet<>(crowRoleRepository.findAllById(
                userRoles.stream()
                        .map(CrowUserRole::getForeignId).collect(Collectors.toList()
                )
        ));
    }

    public Set<Permission> getPermissions(CrowUser user) {
        Set<CrowRole> roles = getRoles(user);
        List<Integer> rolesId = roles.stream().map(CrowRole::getId).collect(Collectors.toList());
        List<CrowAuthority> rolePerms = crowAuthorityRepository.findAllByRoleIdIn(rolesId);
        if(rolePerms.size() == 0) {
            return new HashSet<>();
        }

        Map<String, CrowPermissionResource> nodes = crowPermissionResourceService.getAllNodes()
                .stream().collect(Collectors.toMap(
            CrowPermissionResource::getId,
            item-> item
        ));

        Set<Permission> permissions = new HashSet<>();
        rolePerms.forEach(rp -> {
            if(!nodes.containsKey(rp.getPermissionResource())) {
                return;
            }
            CrowPermission perm = new CrowPermission(
                    rp.getAction(),
                    nodes.get(rp.getPermissionResource()).getResource(),
                    rp.getRowPermissionLevel()
            );
            permissions.add(perm);
        });

        return permissions;
    }

    public void assignRole(ICrowUser<?> crowUser, Set<CrowRole> roles) {
        crowUserRoleRepository.deleteAllByUserIdAndOrgId(crowUser.getId(), crowUser.getOrganizationId());
        List<CrowUserRole> userRoles = new ArrayList<>();
        roles.forEach(role -> {
            CrowUserRole crowUserRole = new CrowUserRole();
            crowUserRole.setUserId(crowUser.getId());
            crowUserRole.setForeignId(role.getId());
            crowUserRole.setOrgId(crowUser.getOrganizationId());
            userRoles.add(crowUserRole);
        });
        crowUserRoleRepository.saveAll(userRoles);
    }

    public void assignRole(ICrowUser<?> crowUser, CrowRole role) {
        Set<CrowRole> exists = getRoles(crowUser);
        exists.add(role);

        assignRole(crowUser, exists);
    }

    public CrowUser getOne(CrowUser user) {
        Set<Permission> permission = getPermissions(user);
        user.setRoles(getRoles(user));
        user.setPermissions(permission);
        return user;
    }

    public Collection<CrowUser> addRolesToList(Collection<CrowUser> users) {
        List<Integer> userIds = users.stream().map(CrowUser::getId).collect(Collectors.toList());

        Map<Integer, List<Integer>> userRoleIdMap = crowUserRoleRepository.findAllByUserIdIn(userIds).stream().collect(Collectors.groupingBy(
                CrowUserRole::getUserId,
                Collectors.mapping(CrowUserRole::getForeignId, Collectors.toList())
        ));

        Map<Integer, CrowRole> rolesMap = crowRoleRepository.findAll().stream().collect(Collectors.toMap(
                CrowRole::getId,
                item -> item
        ));

        users.forEach(user -> {
            HashSet<CrowRole> roles = new HashSet<>();
            userRoleIdMap.getOrDefault(user.getId(), new ArrayList<>()).forEach(id -> {
                roles.add(rolesMap.get(id));
            });
            user.setRoles(roles);
        });

        return users;
    }
}
